package de.bht.fb6.cg1.exercise3.implement;

import de.bht.fb6.cg1.exercise3.Matrix;
import de.bht.fb6.cg1.exercise3.Ring;
import de.bht.fb6.cg1.exercise3.SquareMatrix;

/**
 * a {@link SquareMatrix} implementation
 *
 * @param <T> generalised by a number
 */
@SuppressWarnings("serial")
public class SquareMatrixImpl<T extends Number> extends MatrixImpl<T> implements SquareMatrix<T> {

	/**
	 * Creates a matrix to a given two-dimensional array.
	 * 
	 * @param <R>   RingType of the matrix - not null
	 * @param type  Type of the values - not null
	 * @param vals  Values of the matrix - not null
	 */
	public <R extends Ring<T>> SquareMatrixImpl(final T[][] vals, final Class<R> type) {
		super(vals, type);
		if (vals.length != vals[0].length) {
			throw new IllegalArgumentException("Thats not a Square Matrix!");
		}
	}

	/**
	 * create a new square matrix from a ring array
	 * @param vals values to set - not null
	 */
	public SquareMatrixImpl(final Ring<T>[][] vals) {
		super(vals);
		if (vals.length != vals[0].length) {
			throw new IllegalArgumentException("Thats not a Square Matrix!");
		}
	}

	/**
	 * Copies a Matrix.
	 * 
	 * this is used to get a {@link MatrixImpl} from a matrix.
	 * 
	 * @param <R>   RingType of the Matrix - not null
	 * @param vals  Matrix values - not null
	 * @param type  Type of the values - not null
	 */
	public <R extends Ring<T>>SquareMatrixImpl(final Matrix<T> matrix, final Class<R> type) {
		super(matrix, type);
		if (matrix.getRows() != matrix.getColumns()) {
			throw new IllegalArgumentException("Thats not a Square Matrix!");
		}
	}

	/**
	 * copy a matrix in a square matrix
	 * @param matrix matrix to copy
	 */
	public SquareMatrixImpl(final MatrixImpl<T> matrix) {
		super(matrix);
		if (matrix.getRows() != matrix.getColumns()) {
			throw new IllegalArgumentException("Thats not a Square Matrix!");
		}
	}

	/**
	 * copy a {@link SquareMatrix}
	 * @param matrix matrix to copy
	 */
	public SquareMatrixImpl(final SquareMatrixImpl<T> matrix) {
		super(matrix);
	}

	/**
	 * This method returns if the current matrix is the identity matrix
	 * O(n*m)
	 * 
	 * @return return true if the current matrix is the identity matrix and false if not
	 */
	@Override
	public boolean isIdentityMatrix() {
		final Matrix<T> identity = createId(getRows(), getColumns());
		for (int i = 0; i < getRows(); i++) {
			for (int j = 0; j < getColumns(); j++) {
				if (get(i,j) != identity.get(i,j)) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Calculate the determinant of the presented Matrix and returns as an
	 * operable ring element.
	 * We use the easy to program laplace expansion and do not need division.
	 * 
	 * O(n!)
	 * @return determinant as {@link Ring}-type
	 */
	protected Ring<T> getRingDeterminant() {
		if (getRows() == 1) {
			return getRing(0,0);
		}
		final SquareMatrixImpl<T> A = new SquareMatrixImpl<T>(getMinor(0,0));
		Ring<T> det = getRing(0,0);
		det = det.times(A.getRingDeterminant());
		for (int col = 1; col < getColumns(); col++) {
			if (col % 2 == 1) {
				final SquareMatrixImpl<T> B = new SquareMatrixImpl<T>(getMinor(0,col));
				det = det.sub(getRing(0,col).times(B.getRingDeterminant()));
			} else {
				final SquareMatrixImpl<T> B = new SquareMatrixImpl<T>(getMinor(0,col));
				det = det.add(getRing(0,col).times(B.getRingDeterminant()));
			}
		}
		return det;
	}

	/**
	 * get the determinant
	 * 
	 * @see SquareMatrixImpl#getRingDeterminant() for more details
	 * @return determinant value
	 */
	public T getDeterminant() {
		return getRingDeterminant().get();
	}

	/**
	 * calculate the adjugate matrix
	 * @return get adjugate matrix
	 */
	public SquareMatrixImpl<T> getAdjugate() {
		final Ring<T>[][] result = newRingArray(getRows(), getColumns());
		for (int i = 0; i < getRows(); i++) {
			for (int j = 0; j < getColumns(); j++) {
				if ((i + j) % 2 == 0) {
					result[i][j] = new SquareMatrixImpl<T>(getMinor(i,j)).getRingDeterminant();
				} else {
					result[i][j] = RingZero().sub(new SquareMatrixImpl<T>(getMinor(i,j)).getRingDeterminant());
				}
			}
		}
		return new SquareMatrixImpl<T>(new SquareMatrixImpl<T>(result).getTransposed());
	}
}
